[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto
skywatched.app
5.5 kB
166 lines
1<script lang="ts">
2 import { type PageData } from './$types';
3 import { cn } from '$lib/utils';
4 import { rateMovieModal, videoPlayer, watchedItems } from '$lib/state.svelte';
5
6 import Container from '$lib/Components/Container.svelte';
7 import ItemsList from '$lib/Components/ItemsList.svelte';
8 import ReviewList from '$lib/Components/ReviewList.svelte';
9 import Avatar from '$lib/Components/Avatar.svelte';
10
11 let { data }: { data: PageData } = $props();
12</script>
13
14<svelte:head>
15 <title>{data.result.title ?? data.result.name ?? ''} | skywatched</title>
16</svelte:head>
17
18{#snippet buttons()}
19 {#if data.user && !watchedItems.hasRated(data.result)}
20 <button
21 class={cn(
22 'inline-flex items-center gap-2 rounded-md bg-white/10 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-75 hover:bg-white/20 ',
23 watchedItems.hasRated(data.result)
24 ? 'bg-green-500/10 text-green-400 hover:bg-green-500/20'
25 : ''
26 )}
27 onclick={() => {
28 rateMovieModal.show({
29 movieId: data.result.movieId,
30 showId: data.result.showId,
31 kind: data.result.movieId ? 'movie' : 'tv',
32 name: data.result.original_title ?? data.result.original_name,
33 posterPath: data.result.poster_path,
34 currentRating: watchedItems.getRating(data.result)?.rating ?? 0,
35 currentReview: watchedItems.getRating(data.result)?.ratingText ?? ''
36 });
37 }}
38 >
39 rate {data.kind === 'movie' ? 'movie' : 'show'}
40 </button>
41 {/if}
42 {#if data.trailer}
43 <button
44 onclick={() => videoPlayer.show(data.trailer ?? '')}
45 type="button"
46 class="inline-flex w-fit items-center gap-x-1.5 rounded-md border border-accent-500/30 bg-accent-700/20 px-3 py-2 text-sm font-semibold text-accent-400 shadow-sm transition-all duration-100 hover:bg-accent-700/30 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600"
47 >
48 <svg
49 xmlns="http://www.w3.org/2000/svg"
50 viewBox="0 0 24 24"
51 fill="currentColor"
52 class="-ml-0.5 size-5"
53 >
54 <path
55 d="M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z"
56 />
57 </svg>
58
59 Trailer
60 </button>
61 {/if}
62{/snippet}
63
64<img
65 src="https://image.tmdb.org/t/p/w780{data.result.backdrop_path}"
66 alt=""
67 class="fixed h-full w-full object-cover object-center opacity-20"
68/>
69<div class="fixed inset-0 h-full w-full bg-black/50"></div>
70
71<Container class="relative z-10 pb-8 pt-4">
72 <div class="flex gap-4 px-4 pt-8">
73 <img
74 src="https://image.tmdb.org/t/p/w500{data.result.poster_path}"
75 alt=""
76 class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44"
77 style:--name={`poster-${data.result.id}`}
78 />
79 <div class="flex flex-col gap-4">
80 <div
81 class="title max-w-xl text-2xl font-semibold text-white sm:text-4xl"
82 style:--name={`title-${data.result.id}`}
83 >
84 {data.result.title ?? data.result.name}
85 </div>
86
87 {#if data.settings?.streaming_region?.code && data.watchProviders[data.settings.streaming_region.code]?.flatrate}
88 <div class="mt-2 text-sm text-white sm:mt-4">
89 <div class="mb-2 flex flex-wrap gap-4 text-xs font-medium">
90 stream on
91 <span class="flex items-center gap-2 text-base-400"
92 >from <a
93 href="https://www.justwatch.com"
94 target="_blank"
95 class="text-base-300 hover:text-accent-300"
96 >
97 <img src="/justwatch_logo.svg" alt="justwatch" class="h-3" />
98 </a></span
99 >
100 </div>
101 <a
102 href={data.watchProviders[data.settings.streaming_region.code].link}
103 target="_blank"
104 class="flex flex-wrap gap-2"
105 >
106 {#each data.watchProviders[data.settings.streaming_region.code].flatrate as provider}
107 <img
108 src="https://image.tmdb.org/t/p/w500{provider.logo_path}"
109 alt={provider.provider_name}
110 class="size-8 rounded-md border border-base-800 md:size-12"
111 />
112 {/each}
113 </a>
114 </div>
115 {/if}
116 <div class="hidden gap-2 sm:flex">
117 {@render buttons()}
118 </div>
119 </div>
120 </div>
121
122 <div class="px-4 pt-4 text-sm text-white">
123 <div class="mb-4 flex gap-2 sm:hidden">
124 {@render buttons()}
125 </div>
126 <div class="mb-4 max-w-2xl text-sm text-white">
127 <div class="mb-2 text-lg font-semibold">overview</div>
128 {data.result.overview}
129 </div>
130 </div>
131
132 {#if data.ratings.length > 0}
133 <div class="mt-8 px-4 text-lg font-semibold">reviews</div>
134
135 <ReviewList reviews={data.ratings} showMovieDetails={false} class="" />
136 {/if}
137
138 {#if data.recommendations.length > 0}
139 <div class="px-4 pt-4 text-sm text-white">
140 <div class="mb-2 text-lg font-semibold">recommendations</div>
141
142 <ItemsList items={data.recommendations} showMark={!!data.user} />
143 </div>
144 {/if}
145
146 {#if data.cast.length > 0}
147 <div class="px-4 pb-8 pt-4 text-sm text-white">
148 <div class="mb-2 text-lg font-semibold">cast</div>
149
150 <div class={cn('flex gap-x-6 overflow-x-auto')}>
151 {#each data.cast as castMember}
152 <a href={`/cast/${castMember.id}`} class="flex flex-col items-center gap-1">
153 <Avatar
154 src={castMember.profile_path
155 ? 'https://image.tmdb.org/t/p/w500' + castMember.profile_path
156 : undefined}
157 size="size-32"
158 />
159 <div class="text-center text-xs font-medium">{castMember.name}</div>
160 <div class="text-center text-xs text-base-400">{castMember.character}</div>
161 </a>
162 {/each}
163 </div>
164 </div>
165 {/if}
166</Container>